﻿using Engine;
using Engine.DynamicObjectManager;
using Engine.FileFormats3D;
using Engine.ObjectManager;
using Engine.Shape;
using Engine.TrafficHelpers;
using Scenario_Editor.Tools;
using SharpDX;
using System;
using System.Collections.Generic;

namespace CustomRoad
{
    public struct MeshData
    {
        #region Private Fields

        private List<CrossroadTraffic> crossroadTraffic;
        private List<BaseObject> dynamicObjectBaseNodes;
        private Mesh3dData mesh;
        private float minX, maxX, minXVertex, maxXVertex, minXShape, maxXShape, minXObjects, maxXObjets;
        private List<BaseNode> objectNodes;
        private Shapes shape;

        #endregion Private Fields

        #region Public Constructors

        public MeshData(Mesh3dData mesh, Shapes shape, List<CrossroadTraffic> crossroadTraffic, List<BaseObject> dynObjects, List<BaseNode> objectNodes)
        {
            bool shapeIsPresent = false;
            this.mesh = mesh;
            this.shape = shape;
            this.minX = float.MaxValue;
            this.minXObjects = float.MaxValue;
            this.minXShape = float.MaxValue;
            this.minXVertex = float.MaxValue;

            this.maxX = float.MinValue;
            this.maxXObjets = float.MinValue;
            this.maxXShape = float.MinValue;
            this.maxXVertex = float.MinValue;

            this.crossroadTraffic = new List<CrossroadTraffic>(crossroadTraffic);
            this.dynamicObjectBaseNodes = new List<BaseObject>(dynObjects);
            this.objectNodes = new List<BaseNode>(objectNodes);

            // minimum position of mesh.
            for (int i = 0; i < this.mesh.Vertices.Count; i++)
            {
                this.minXVertex = Math.Min(this.mesh.Vertices[i].X, this.minXVertex);
                this.maxXVertex = Math.Max(this.mesh.Vertices[i].X, this.maxXVertex);
            }

            // Get boundaries of shape.
            for (int i = 0; i < this.shape.GetBorders().Length; i++)
            {
                Border border = this.shape.GetBorder(i);
                for (int j = 0; j < border.GetNumberOfPositions(); j++)
                {
                    this.minXShape = Math.Min(border.GetPositionAt(j).X, this.minXShape);
                    this.maxXShape = Math.Max(border.GetPositionAt(j).X, this.maxXShape);
                }
                if (border.GetNumberOfPositions() > 0)
                    shapeIsPresent = true;
            }

            // Get the boundaries of the dynamic objects.
            for (int i = 0; i < this.DynamicObjectBaseObjects.Count; i++)
            {
                this.minXObjects = Math.Min(this.DynamicObjectBaseObjects[i].PositionX, this.minXObjects);
                this.maxXObjets = Math.Max(this.DynamicObjectBaseObjects[i].PositionX, this.maxXObjets);
            }

            this.MaxX = Math.Max(this.MaxX, this.maxXObjets);
            this.MinX = Math.Min(this.MinX, this.minXObjects);

            if (this.mesh.Vertices.Count > 0)
            {
                this.MaxX = Math.Max(this.MaxX, this.maxXVertex);
                this.MinX = Math.Min(this.MinX, this.minXVertex);
            }
            if (shapeIsPresent)
            {
                this.MaxX = Math.Max(this.MaxX, this.maxXShape);
                this.MinX = Math.Min(this.MinX, this.minXShape);
            }

            if (this.maxX - this.minX == 0)
                this.maxX = 10.0f;
        }

        #endregion Public Constructors

        #region Public Properties

        public List<CrossroadTraffic> CrossroadTraffic
        {
            get { return crossroadTraffic; }
            set { crossroadTraffic = value; }
        }

        public List<BaseObject> DynamicObjectBaseObjects
        {
            get { return dynamicObjectBaseNodes; }
            set { dynamicObjectBaseNodes = value; }
        }

        public float MaxX
        {
            get { return maxX; }
            set { maxX = value; }
        }

        public Mesh3dData Mesh
        {
            get { return mesh; }
            set { mesh = value; }
        }

        public float MinX
        {
            get { return minX; }
            set { minX = value; }
        }

        public List<BaseNode> ObjectNodes
        {
            get { return objectNodes; }
            set { objectNodes = value; }
        }

        public Shapes Shape
        {
            get { return shape; }
            set { shape = value; }
        }

        #endregion Public Properties

        #region Internal Methods

        internal IEnumerable<BaseObject> GetTransformedDynamicObjects(ref List<Vector3> middleline,
            ref List<Vector2> inclinationVectors,
            bool inverseInclination,
            ref Vector3 directionStart,
            ref Vector3 directionEnd,
            List<BaseObject> objects,
            List<BaseNode> nodes,
            bool ScaleToSegmentLength,
            float minX, float maxX, ITerrain iTerrain)
        {
            if (objects.Count > 0)
            { }

            // This is the smallest X-Coordinate of all positions from this border.
            float xOffset = 0;

            // current distance on the middleline.
            float distance = 0;

            // Transformed BaseNodes which will returned.
            List<BaseObject> transformedbaseNodes = new List<BaseObject>();

            // Created new borders which will later be transformed.

            for (int i = 0; i < objects.Count; i++)
                transformedbaseNodes.Add(new BaseObject(
                    objects[i].Name,
                    objects[i].PositionX,
                    objects[i].PositionY,
                    objects[i].PositionZ,
                    objects[i].AngleY,
                    objects[i].Size));

            // Calculate distances between the middleline positions.
            List<float> distancesMiddleLine = new List<float>();
            List<Vector3> directionToNextPoint = new List<Vector3>();

            // Get the minimum x position.
            for (int i = 0; i < transformedbaseNodes.Count; i++)
            {
                xOffset = Math.Min(xOffset, transformedbaseNodes[i].PositionX);
            }

            /* Use the smallest X value. */
            xOffset = Math.Min(minX, xOffset);

            float meshLengthX = 0;

            /* Calculates the segment length and
             * added the x-Offset to the vertexPositions. */
            for (int i = 0; i < transformedbaseNodes.Count; i++)
            {
                float vertexPosX = transformedbaseNodes[i].PositionX; // Get the current position.

                vertexPosX = Math.Max(0, vertexPosX - xOffset);// Added the offset to the vertexposition. It should not be less 0. It´s a workaround when it is less than 0.

                transformedbaseNodes[i].PositionX = vertexPosX; // Set the new vertex position with the offset.
                meshLengthX = Math.Max(meshLengthX, vertexPosX); // Calculates the segment length.
            }

            /* Get the complete Segment Length. */
            meshLengthX = Math.Max(maxX - minX, meshLengthX);

            /* Added the distances from one point to next point for the middle line. */
            distancesMiddleLine.Add(0); // first Point starts at zero.
            for (int i = 0; i < middleline.Count - 1; i++)
            {
                distance += GeoMath.DistanceBtw2Points(middleline[i], middleline[i + 1]);
                distancesMiddleLine.Add(distance);
            }

            if (ScaleToSegmentLength && distancesMiddleLine[1] > 0.0f) // If scaled to next point.
            {
                for (int i = 0; i < transformedbaseNodes.Count; i++)
                {
                    transformedbaseNodes[i].ScaleX(distancesMiddleLine[1] / meshLengthX);

                    float tempPosX = transformedbaseNodes[i].PositionX;
                    tempPosX = Math.Min(tempPosX, distancesMiddleLine[1]);
                    transformedbaseNodes[i].PositionX = tempPosX;
                }
                meshLengthX *= distancesMiddleLine[1] / meshLengthX;
                meshLengthX = Math.Min(distancesMiddleLine[distancesMiddleLine.Count - 1], meshLengthX);
            }
            else if (meshLengthX / distance > 1) // border will be transformed if the border is longer than the rest of the middleline.
            {
                for (int i = 0; i < transformedbaseNodes.Count; i++)
                {
                    transformedbaseNodes[i].ScaleX(distance / meshLengthX);

                    float tempX = transformedbaseNodes[i].PositionX;
                    tempX = Math.Min(tempX, distance);
                    transformedbaseNodes[i].PositionX = tempX;
                }

                meshLengthX *= distance / meshLengthX;
                meshLengthX = Math.Min(distance, meshLengthX);
            }

            // Calculates the directions between the middleline-points.
            directionToNextPoint.Add(directionStart);
            for (int i = 1; i < middleline.Count - 1; i++)
            {
                directionToNextPoint.Add(Vector3.Normalize(middleline[i + 1] - middleline[i - 1]));
            }
            directionToNextPoint.Add(directionEnd);

            /* Gets all possible X-Positions will will later added to the middlelLine */
            List<float> allXPositions = new List<float>();
            for (int i = 0; i < transformedbaseNodes.Count; i++)
            {
                float xPosition = transformedbaseNodes[i].PositionX;
                if (!allXPositions.Contains(xPosition))
                    allXPositions.Add(xPosition);
            }

            if (!allXPositions.Contains(meshLengthX))
                allXPositions.Add(meshLengthX);

            allXPositions.Sort();
            // Add the x Positions to the  middleLine when it isn´t already in the list.
            for (int i = 0; i < allXPositions.Count; i++)
            {
                if (!distancesMiddleLine.Contains(allXPositions[i]))
                {
                    AddPointToMiddleLine(middleline, inclinationVectors, distancesMiddleLine, directionToNextPoint, allXPositions[i]);
                }
            }

            /* Calculate the new Position of the vertex. */
            int lastUsedMiddleLinePointIndex = 0;
            for (int i = 0; i < transformedbaseNodes.Count; i++)
            {
                Vector3 vertex;// = transformedMesh[i].positions[j];

                int index = distancesMiddleLine.IndexOf(transformedbaseNodes[i].PositionX);

                if (index > -1) // When no point found on middleLine
                {
                    //lastUsedMiddleLinePoint = Math.Max(lastUsedMiddleLinePoint, index);
                }
                else
                    index = middleline.Count - 1;

                lastUsedMiddleLinePointIndex = Math.Max(lastUsedMiddleLinePointIndex, index);
                Vector3 positionOnMiddleLine = middleline[index];
                Vector3 directionVector = directionToNextPoint[index];
                directionVector.Normalize();
                Vector2 inclinationOfRoad = inclinationVectors[index];

                Vector2 direction2D = new Vector2(directionVector.X, directionVector.Z);
                direction2D.Normalize();

                float length = (float)Math.Sqrt(directionVector.X * directionVector.X + directionVector.Z * directionVector.Z) - (float)Math.Sqrt(directionVector.Y * directionVector.Y);

                // Start with the middleroad position
                vertex = positionOnMiddleLine;

                // add the mesh vertex postion
                Vector3 vertexAfterInclination = GeoMath.AddInclinationToVector3(inverseInclination, inclinationOfRoad, new Vector3(objects[i].PositionX, objects[i].PositionY, objects[i].PositionZ));

                vertex.X += -direction2D.Y * vertexAfterInclination.Z;
                vertex.Y += vertexAfterInclination.Y * (length + Math.Abs(directionVector.Y));
                vertex.Z += direction2D.X * vertexAfterInclination.Z;

                // add the inclination
                vertex.X += vertexAfterInclination.Y * directionVector.Y * (-directionVector.X);
                vertex.Z += vertexAfterInclination.Y * directionVector.Y * (-directionVector.Z);

                transformedbaseNodes[i].PositionX = vertex.X;
                transformedbaseNodes[i].PositionY = vertex.Y;
                transformedbaseNodes[i].PositionZ = vertex.Z;
                transformedbaseNodes[i].AngleY += (float)Math.Atan2(-(double)direction2D.Y, (double)direction2D.X);

                if (nodes[i].SetToTerrain)
                    transformedbaseNodes[i].PositionY = iTerrain.GetHeightFromPosition(new Vector3(transformedbaseNodes[i].PositionX, transformedbaseNodes[i].PositionY, transformedbaseNodes[i].PositionZ));

                transformedbaseNodes[i].PositionY += nodes[i].HeightOverTerrain;
            }

            lastUsedMiddleLinePointIndex = Math.Max(lastUsedMiddleLinePointIndex, distancesMiddleLine.IndexOf(meshLengthX));

            middleline.RemoveRange(0, lastUsedMiddleLinePointIndex);
            directionToNextPoint.RemoveRange(0, lastUsedMiddleLinePointIndex);
            inclinationVectors.RemoveRange(0, lastUsedMiddleLinePointIndex);

            if (middleline.Count == 1)
                middleline.Clear();

            if (directionToNextPoint.Count > 1)
            {
                directionStart = directionToNextPoint[0];
                directionEnd = directionToNextPoint[directionToNextPoint.Count - 1];
            }

            return transformedbaseNodes;
        }

        #endregion Internal Methods

        #region Private Methods

        private void AddPointToMiddleLine(List<Vector3> middleline, List<Vector2> inclinationVectors, List<float> distancesMiddleLine, List<Vector3> directionToNextPoint, float positionX)
        {
            for (int j = 0; j < distancesMiddleLine.Count; j++)
            {
                if (positionX < distancesMiddleLine[j])
                {
                    //Add the new XPosition to the middleline and calculate the angle and all
                    // other data if needed.
                    float value = (positionX - distancesMiddleLine[j - 1]) / (distancesMiddleLine[j] - distancesMiddleLine[j - 1]);
                    float distanceToPreviewPoint = positionX - distancesMiddleLine[j - 1];
                    Vector3 differenceVector = Vector3.Subtract(directionToNextPoint[j], directionToNextPoint[j - 1]);
                    distancesMiddleLine.Insert(j, positionX);

                    // add a new Point to the middleLine.
                    Vector3 direction = Vector3.Normalize(middleline[j] - middleline[j - 1]);

                    middleline.Insert(j, middleline[j - 1] + direction * distanceToPreviewPoint);

                    // insert new inclination Point.
                    inclinationVectors.Insert(j, Vector2.Add(inclinationVectors[j - 1], (Vector2.Multiply(inclinationVectors[j] - inclinationVectors[j - 1], value))));

                    directionToNextPoint.Insert(j, Vector3.Subtract(directionToNextPoint[j], differenceVector * (1.0f - value)));
                    break;
                }
            }
        }

        #endregion Private Methods
    }
}